﻿using Service.Domain.Interfaces;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using System.Linq;

namespace Service.Repository
{
    public class DbContext : IDbContext
    {
        private IDbConnection Connection { get; set; }

        private IDbTransaction transaction = null;
        private int? commandTimeout = 30000;
        readonly DapperConnectionFactory _factory;

        private IDbConnection Db
        {
            get
            {
                if (this.Connection == null)
                {
                    this.Connection = _factory.CreateDbConnection();
                }
                if (this.Connection.State == ConnectionState.Broken || this.Connection.State == ConnectionState.Closed)
                {
                    this.Connection.Open();
                }
                return this.Connection;
            }
        }

        public DbContext(DapperConnectionFactory factory)
        {
            this._factory = factory;
        }

        /// <summary>
        /// 设置启动事务
        /// </summary>
        /// <returns></returns>
        public IDbContext UseTransaction()
        {
            this.transaction = this.Db.BeginTransaction();
            return this;
        }

        /// <summary>
        /// 设置sql执行的超时时间
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public IDbContext WithTimeout(int timeout)
        {
            this.commandTimeout = timeout;
            return this;
        }

        public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true)
        {
            return await this.Db.QueryAsync<T>(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)));
        }

        public async Task<T> QueryFirstAsync<T>(string sql, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true)
        {
            return await this.Db.QueryFirstAsync<T>(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)));
        }

        public async Task<T> QueryFirstOrDefaultAsync<T>(string sql, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true)
        {
            return await this.Db.QueryFirstOrDefaultAsync<T>(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)));
        }

        public async Task<T> QuerySingleAsync<T>(string sql, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true)
        {
            return await this.Db.QuerySingleAsync<T>(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)));
        }

        public async Task<T> QuerySingleOrDefaultAsync<T>(string sql, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true)
        {
            return await this.Db.QuerySingleOrDefaultAsync<T>(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)));
        }

        public async Task<int> InsertAsync(string sql, object parameters)
        {
            var res = await this.Db.ExecuteAsync(new CommandDefinition(sql, parameters, this.transaction, this.commandTimeout, CommandType.Text));
            return this.Db.QueryFirst<int>("select LAST_INSERT_ID();");
        }

        public async Task<int> UpdateAsync(string sql, object parameters)
        {
            return await this.Db.ExecuteAsync(new CommandDefinition(sql, parameters, this.transaction, this.commandTimeout, CommandType.Text));
        }

        public async Task<int> DeleteAsync(string sql, object parameters)
        {
            return await this.Db.ExecuteAsync(new CommandDefinition(sql, parameters, this.transaction, this.commandTimeout, CommandType.Text));
        }

        public void Commit()
        {
            if (this.transaction != null)
            {
                this.transaction.Commit();
            }
            else
            {
                throw new Exception();
            }
        }

        public void Rollback()
        {
            if (this.transaction != null)
            {
                this.transaction.Rollback();
            }
            else
            {
                throw new Exception();
            }
        }

        public void Dispose()
        {
            if (this.Db.State == ConnectionState.Open || this.Db.State == ConnectionState.Broken)
            {
                this.Db.Close();
            }
        }

        private CommandFlags GetCommandFlag(bool buffered)
        {
            return buffered ? CommandFlags.Buffered : CommandFlags.None;
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, TResult>(string sql, Func<T, T2, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, T3, TResult>(string sql, Func<T, T2, T3, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, T3, T4, TResult>(string sql, Func<T, T2, T3, T4, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, T3, T4, T5, TResult>(string sql, Func<T, T2, T3, T4, T5, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, T3, T4, T5, T6, TResult>(string sql, Func<T, T2, T3, T4, T5, T6, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<IEnumerable<TResult>> QueryAsync<T, T2, T3, T4, T5, T6, T7, TResult>(string sql, Func<T, T2, T3, T4, T5, T6, T7, TResult> func, object parameters = null, CommandType cmdType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, cmdType, GetCommandFlag(buffered)), func, splitOn);
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, TResult>(string sql, Func<T1, T2, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, T3, TResult>(string sql, Func<T1, T2, T3, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, T3, T4, TResult>(string sql, Func<T1, T2, T3, T4, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, T3, T4, T5, TResult>(string sql, Func<T1, T2, T3, T4, T5, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, T3, T4, T5, T6, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstAsync<T1, T2, T3, T4, T5, T6, T7, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().First();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, TResult>(string sql, Func<T1, T2, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, T3, TResult>(string sql, Func<T1, T2, T3, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, T3, T4, TResult>(string sql, Func<T1, T2, T3, T4, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, T3, T4, T5, TResult>(string sql, Func<T1, T2, T3, T4, T5, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, T3, T4, T5, T6, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QueryFirstOrDefaultAsync<T1, T2, T3, T4, T5, T6, T7, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().FirstOrDefault();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, TResult>(string sql, Func<T1, T2, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, T3, TResult>(string sql, Func<T1, T2, T3, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, T3, T4, TResult>(string sql, Func<T1, T2, T3, T4, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, T3, T4, T5, TResult>(string sql, Func<T1, T2, T3, T4, T5, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, T3, T4, T5, T6, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleAsync<T1, T2, T3, T4, T5, T6, T7, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().Single();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, TResult>(string sql, Func<T1, T2, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, T3, TResult>(string sql, Func<T1, T2, T3, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, T3, T4, TResult>(string sql, Func<T1, T2, T3, T4, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, T3, T4, T5, TResult>(string sql, Func<T1, T2, T3, T4, T5, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, T3, T4, T5, T6, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }

        public async Task<TResult> QuerySingleOrDefaultAsync<T1, T2, T3, T4, T5, T6, T7, TResult>(string sql, Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, object parameters = null, CommandType commandType = CommandType.Text, bool buffered = true, string splitOn = "Id")
        {
            return (await this.Db.QueryAsync(new CommandDefinition(sql, parameters, transaction, commandTimeout, commandType, GetCommandFlag(buffered)), func, splitOn)).AsList().SingleOrDefault();
        }
    }
}
